#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import os.path
import re
import shutil
import socket

from common.common import get_local_ips, execute_cmd, check_path_legal, output_execution_result_ex, \
    check_command_injection
from common.const import AuthType, CMDResult, ParamConstant
from common.logger import Logger
from common.number_const import NumberConst
from generaldb.saphana.comm.saphana_common_function import CommonFuction
from saphana.resource.saphana_cluster_manager import SaphanaClusterManage
from generaldb.saphana.comm.saphana_const import SaphanaErrorCode, SaphanaJsonConstant, SaphanaConst, \
    SaphanaDbActionType
from saphana.common.param_parse import Param
from common.cleaner import clear
from saphana.common.saphana_const import SapConst
from common.const import RoleType
from saphana.resource.common_util import HanaCommonUtil
from common.parse_parafile import get_env_variable, add_env_param

LOGGER = Logger().get_logger(filename="saphana.log")


class CheckApplication:
    def __init__(self, pid, params: Param, manager: SaphanaClusterManage = None):
        self.pid = pid
        self.context = params
        self.application = self.context.application
        self.sid = self.context.app_extend_info.get("systemId", "")
        self.type = self.context.app_extend_info.get("sapHanaDbType", "")
        self.name = self.application.get("name")
        self.enable_log_backup = self.context.app_extend_info.get("enableLogBackup", "").lower()
        self.operation_type = self.context.app_extend_info.get("operationType", "")
        self._log_bak_ex_info = json.loads(self.context.app_extend_info.get("logBackupExtInfo", "{}"))
        self.log_path: str = self._log_bak_ex_info.get("logBackupPath", "")
        self.resource_type = self.context.sub_type
        self.nodes = json.loads(self.context.app_extend_info.get("nodes", "[]"))
        self.role_id = ""
        self._cluster_manager = manager
        self.db_pwd_env = SaphanaJsonConstant.DB_PASSWORD + self.pid
        self.db_proctect_username_env = SaphanaJsonConstant.SYSTEM_DB_USER + self.pid
        self.db_protect_passord_env = SaphanaJsonConstant.SYSTEM_DB_PASSWORD + self.pid
        self.system_db_port = self.context.app_extend_info.get(SaphanaJsonConstant.SYSTEM_DB_PORT, "")
        self.system_instance_db_port = self.context.app_extend_info.get("instanceSystemDbPort", "")
  
    def check_system_database(self, manager):
        ret = manager.is_system_db()
        if not ret:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.TENANT_MODEL_NOT_SUPPORT,
                                                "It is not systemdb!")
            LOGGER.error("It is not systemdb!")
            return params
        self.resource_type = SapConst.SAPHANA_INSTANCE
        ret, params = self.login_saphana_authentication(manager)
        if not ret:
            return params
        success_message = "Systemdb!"
        params = Param.get_body_error_param(SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                            SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                            success_message)
        return params

    def check_saphana_instance(self, manager: SaphanaClusterManage):
        """查询实例id是否存在"""
        ret, _ = HanaCommonUtil.get_instance_id(self.sid)
        if not ret:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.SYSTEM_ID_NOT_EXIST,
                                                "System id not exists!")
            LOGGER.error("System id not exists!")
            return params
        """使用认证方式认证"""
        ret, params = self.login_saphana_authentication(manager)
        if not ret:
            return params
        if self.operation_type == "TestConnectivity":
            params = Param.get_body_error_param(SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                                SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                                "")
            return params
        return self.assembly_parameters(manager)

    def check_saphana_database(self, manager: SaphanaClusterManage):
        if self.type == "SystemDatabase":
            return self.check_system_database(manager)
        if self.type == "TenantDatabase":
            return self.check_tenant_database(manager)
        else:
            LOGGER.error("Unknown type from file!")
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.TENANT_MODEL_NOT_SUPPORT,
                                                "Unkown file! ")
            return params

    def login_saphana_authentication(self, manager):
        auth_type = HanaCommonUtil.safe_get_environ(f"{SaphanaJsonConstant.APPLICATION}_{SaphanaJsonConstant.AUTH}_" \
                                                    f"{SaphanaJsonConstant.AUTH_TYPE}_{self.pid}")
        query_log_cmd = f"alter system alter configuration ('global.ini', 'SYSTEM') set ('backup', " \
                        "'log_backup_parameter_file') = " \
                        f"'{self._cluster_manager.get_dir_instance()}/SYS/" \
                        f"global/hdb/opt/hdbconfig/backint.conf' with reconfigure;"
        query_cmd = f"alter system alter configuration ('global.ini', 'SYSTEM') set ('backup', " \
                    f"'data_backup_parameter_file') = '{self._cluster_manager.get_dir_instance()}/SYS" \
                    "/global/hdb/opt/hdbconfig/backint.conf' with reconfigure;"
        if str(auth_type) == str(AuthType.APP_PASSWORD.value):
            ret, params = self.password_authentication(manager, query_cmd, query_log_cmd)
            return ret, params
        if str(auth_type) == str(AuthType.OTHER.value):
            ret, params = self.key_authentication(manager, query_log_cmd, query_cmd)
            return ret, params
        return False, {}

    def check_tenant_database(self, manager):
        ret, params = self.login_saphana_authentication(manager)
        if not ret:
            return params
        success_message = "Tenantdb!"
        params = Param.get_body_error_param(SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                            SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                            success_message)
        return params

    def assembly_parameters(self, manager):
        try:
            version = manager.get_version()
        except Exception as ex:
            error_message = "Get version failed!"
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.DB_VERSION_FAILED,
                                                error_message)
            LOGGER.error(error_message)
            return params
        if self.operation_type != "modify":
            ret, params = HanaCommonUtil.installing_the_backint(self.sid, self._cluster_manager.get_dir_instance())
            if not ret:
                LOGGER.error("install backint failed.")
                return params
        # 检查日志路径在共享目录下
        if (self.enable_log_backup == "true" and
                not os.path.realpath(self.log_path).startswith(self._cluster_manager.get_shared_path())):
            LOGGER.error(f"Log backup path not in shared path. {self.log_path}")
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.LOG_BACKUP_PATH_NOT_SHARED,
                                                "Log backup path not in shared path.")
            return params
        # 配置日志
        ret, params = self._configure_log()
        if not ret:
            return params
        try:
            uuid = manager.get_uuid()
        except Exception as ex:
            error_message = "Get uuid failed!"
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.DB_UUID_FAILED,
                                                error_message)
            LOGGER.error(error_message)
            return params
        message = {"version": version, "landscapeId": uuid, "role": self.role_id}
        try:
            success_message = json.dumps(message)
        except Exception as ex:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.ERROR_PARAM,
                                                str(ex))
            return params
        params = Param.get_body_error_param(SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                            SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                            success_message)
        return params

    def password_authentication(self, manager, query_cmd, query_log_cmd):
        username = HanaCommonUtil.safe_get_environ(SaphanaJsonConstant.DB_USER + self.pid)
        if self.resource_type == SapConst.SAPHANA_INSTANCE:
            query_cmd_list = [query_log_cmd, query_cmd]
            ret, params = self.password_authentication_instance(manager, username, query_cmd_list)
            if not ret:
                return False, params
        if self.resource_type == SapConst.SAPHANA_DATABASE:
            query_cmd_list = []
            if not manager.is_tenant_db_exist(self.db_pwd_env):
                params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                    SaphanaErrorCode.ACCESS_DB_ERROR,
                                                    "Login failed!")
                LOGGER.error("DB not exist or Login failed!")
                return False, params
            args = (self.name, username, self.db_pwd_env, self.sid, self.system_db_port, query_cmd_list)
            ret, error_code, _ = HanaCommonUtil.login_database(*args)
            if not ret:
                params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                    SaphanaErrorCode.ACCESS_DB_ERROR,
                                                    "Login failed!")
                return False, params
            ret, params = self.check_node_for_authentication(manager, username, self.db_pwd_env)
            if not ret:
                return False, params
        return True, {}

    def remove_protect(self):
        resource_id = self.application.get("id", "")
        if not resource_id:
            LOGGER.warning("Fail to get uuid")
            return False
        log_path = self.context.get_log_path_from_app_env
        target_mount_path = os.path.join(log_path, resource_id)
        target_mount_path = os.path.realpath(target_mount_path)
        if re.search(SapConst.PATH_BLACK_LIST, target_mount_path):
            LOGGER.error("Path %s in black list!", target_mount_path)
            return False
        if not os.path.exists(target_mount_path) or not os.path.isdir(target_mount_path) or \
                check_command_injection(target_mount_path):
            LOGGER.warning("Path %s can not umount", target_mount_path)
            return True
        ret, _, err = execute_cmd(f"umount {target_mount_path}")
        if ret != CMDResult.SUCCESS:
            LOGGER.warning("Fail to umount %s for %s", target_mount_path, err)
            return False
        try:
            shutil.rmtree(target_mount_path)
        except Exception as err:
            LOGGER.warning("Fail to remove mount path for resource %s for %s ", resource_id, err)
            return False
        LOGGER.info("Remove protect success.")
        return True

    def check_node_for_authentication(self, manager, username, db_pwd_key):
        endpoint_list: list = []
        agent_list: list = []
        for node in self.nodes:
            endpoint_ip = node.get("endpoint", "")
            agent_ip = node.get("extendInfo", {}).get("agentIpList", "")
            endpoint_list.append(endpoint_ip)
            agent_list.append(agent_ip)
        try:
            manager.check_agent_enough(agent_list, username, db_pwd_key, endpoint_list)
        except Exception as ex:
            LOGGER.info(f"{ex}")
            parameter_list : list = ex.parameter_list
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.DB_NODES_NOT_FULL_MATCH,
                                                "Node is not match",
                                                parameter_list)
            LOGGER.error("Node is not match.")
            return False, params
        return True, {}
    
    def password_authentication_instance(self, manager, username, query_cmd_list):
        args = (SaphanaConst.SYSTEM_DB, username, self.db_pwd_env, self.sid, self.system_db_port, query_cmd_list)
        ret, error_code, _ = HanaCommonUtil.login_database(*args)
        if error_code == SaphanaErrorCode.DB_CONFIG_FAILED:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.DB_CONFIG_FAILED,
                                                "DB config failed!")
            return False, params
        if not ret and error_code != SaphanaErrorCode.DB_CONFIG_FAILED:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.ACCESS_DB_ERROR,
                                                "Login failed!")
            LOGGER.error("Login failed!")
            return False, params
        master_host_ip = socket.gethostbyname(manager.get_master_hostname(self.db_pwd_env))
        ret, params = self.check_node_for_authentication(manager, username, self.db_pwd_env)
        if not ret:
            return False, params
        if master_host_ip in get_local_ips():
            self.role_id = str(RoleType.PRIMARY.value)
        else:
            self.role_id = str(RoleType.STANDBY.value)
        return True, {}
    
    def key_authentication(self, manager, query_log_cmd, query_cmd):
        hdbuserstorekey = HanaCommonUtil.safe_get_environ(f"{SaphanaJsonConstant.APPLICATION}_"
                                                          f"{SaphanaJsonConstant.AUTH}_"
                                                          f"{SaphanaJsonConstant.EXTEND_INFO}_"
                                                          f"{SaphanaJsonConstant.KEY_INFO}_{self.pid}")
        ret = manager.check_hdbuserstorekey(hdbuserstorekey)
        if not ret:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.ACCESS_DB_ERROR,
                                                "Login failed!")
            LOGGER.error("Login failed!")
            return False, params
        add_env_param(self.db_pwd_env, hdbuserstorekey)
        if self.resource_type == SapConst.SAPHANA_DATABASE:
            if not manager.is_tenant_db_exist(self.db_pwd_env):
                params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                    SaphanaErrorCode.ACCESS_DB_ERROR,
                                                    "Login failed!")
                return False, params
        ret, params = self.check_node_for_authentication(manager, "", self.db_pwd_env)
        if not ret:
            return False, params
        if self.resource_type == SapConst.SAPHANA_INSTANCE:
            ret = manager.change_system_config(query_log_cmd, self.db_pwd_env)
            if not ret:
                params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                    SaphanaErrorCode.DB_CONFIG_FAILED,
                                                    "Check log_backup_parameter_file failed!")
                return ret, params
            ret = manager.change_system_config(query_cmd, self.db_pwd_env)
            if not ret:
                params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                    SaphanaErrorCode.DB_CONFIG_FAILED,
                                                    "Check log_backup_parameter_file failed!")
                return ret, params
        LOGGER.info("Hdbuserstore authentication success")
        return True, {}

    def _configure_log(self):
        # 修改日志备份开关
        if self.enable_log_backup == "true":
            enable_log_cmd = SapConst.ENABLE_LOG_CMD
        else:
            enable_log_cmd = SapConst.UNENABLE_LOG_CMD
        if not self._cluster_manager.change_system_config(enable_log_cmd, self.db_pwd_env):
            LOGGER.error("Fail to configure log switch.")
            return False, Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                     SaphanaErrorCode.SYSTEM_ERROR, "")
        catalog_backint_cmd = "alter system alter configuration ('global.ini', 'SYSTEM') " \
                          "set ('backup', 'catalog_backup_using_backint') = 'true';"
        if not self._cluster_manager.change_system_config(catalog_backint_cmd, self.db_pwd_env):
            LOGGER.error("Fail to configure catalog backint.")
            return False, Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                     SaphanaErrorCode.SYSTEM_ERROR, "")
        if self.enable_log_backup == "false":
            LOGGER.info("Log backup disabled.")
            hdbconfig_file = f"{self._cluster_manager.get_dir_instance()}/{SapConst.BACKINT_CONFIG_SUFFIX}"
            with open(hdbconfig_file, "r+") as file:
                file.truncate(0)
            return True, Param.get_body_error_param(SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                                    SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK, "")
        LOGGER.info(f"EnableLogBackup is : %s,LogPath is %s.", self.enable_log_backup, self.log_path)
        # 检测日志备份目录
        ret = HanaCommonUtil.check_logpath(self.sid, self.log_path)
        if not ret:
            return False, Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                     SaphanaErrorCode.PATH_NOT_EXIST, "LogPath not is right!")
        # 配置日志间隔
        unit_map = {'h': 3600, 'm': 60, 's': 1}
        log_backup_interval = self._log_bak_ex_info.get(SaphanaJsonConstant.LOG_BACKUP_INTERVAL, "")
        log_backup_unit = self._log_bak_ex_info.get(SaphanaJsonConstant.LOG_BACKUP_INTERVAL_UNIT, "")
        if not log_backup_unit or not log_backup_interval or log_backup_unit not in unit_map.keys():
            LOGGER.error(f"Log interval %s or unit %s is empty.", log_backup_interval, log_backup_unit)
            return False, Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                     SaphanaErrorCode.ERROR_PARAM, "")
        log_backup_interval = int(log_backup_interval) * unit_map.get(log_backup_unit)
        cmd = f"alter system alter configuration ('global.ini', 'SYSTEM') " \
              f"set ('persistence', 'log_backup_timeout_s') = '{log_backup_interval}';"
        if not self._cluster_manager.change_system_config(cmd, self.db_pwd_env):
            LOGGER.error("Fail to configure log interval.")
            return False, Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                     SaphanaErrorCode.SYSTEM_ERROR, "")
        # 打开日志backint选项
        if not self._cluster_manager.change_system_config(SapConst.LOG_BACKINT_CMD, self.db_pwd_env):
            LOGGER.error("Fail to configure log backint.")
            return False, Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                     SaphanaErrorCode.SYSTEM_ERROR, "")
        # 配置日志备份目录
        hdbconfig_file = f"{self._cluster_manager.get_dir_instance()}/{SapConst.BACKINT_CONFIG_SUFFIX}"
        output_execution_result_ex(hdbconfig_file, {"logBackupPath": self.log_path})
        return True, Param.get_body_error_param(SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK,
                                                SaphanaErrorCode.SUCCESS_FOR_FRAMEWORK, "")
